#include "HttpSession.h"
#include <boost/system/error_code.hpp>

bcos::boostssl::http::HttpSession::HttpSession(uint32_t _httpBodySizeLimit, CorsConfig _corsConfig)
  : m_httpBodySizeLimit(_httpBodySizeLimit), m_corsConfig(std::move(_corsConfig))
{
    HTTP_SESSION(DEBUG) << LOG_KV("[NEWOBJ][HTTPSESSION]", this)
                        << LOG_KV("httpBodySizeLimit", _httpBodySizeLimit)
                        << LOG_KV("corsConfig", _corsConfig.toString());
}

bcos::boostssl::http::HttpSession::~HttpSession()
{
    close();
    HTTP_SESSION(DEBUG) << LOG_KV("[DELOBJ][HTTPSESSION]", this);
}
void bcos::boostssl::http::HttpSession::run()
{
    boost::asio::dispatch(m_httpStream->stream().get_executor(),
        boost::beast::bind_front_handler(&HttpSession::read, shared_from_this()));
}
void bcos::boostssl::http::HttpSession::read()
{
    m_parser.emplace();
    //
    auto httpBodySizeLimit = m_httpBodySizeLimit;
    // set limit to http request size
    m_parser->body_limit(httpBodySizeLimit);

    m_httpStream->asyncRead(m_buffer, *m_parser,
        [session = shared_from_this()](boost::system::error_code _ec,
            std::size_t bytes_transferred) { session->onRead(_ec, bytes_transferred); });
}
void bcos::boostssl::http::HttpSession::onRead(
    boost::beast::error_code ec, std::size_t bytes_transferred)
{
    try
    {
        // the peer client closed the connection
        if (ec == boost::beast::http::error::end_of_stream)
        {
            HTTP_SESSION(TRACE) << LOG_BADGE("onRead") << LOG_DESC("end of stream");
            close();
            return;
        }

        if (ec)
        {
            HTTP_SESSION(WARNING) << LOG_BADGE("onRead") << LOG_DESC("close the connection")
                                  << LOG_KV("ec", ec) << LOG_KV("ec_message", ec.message());
            close();
            return;
        }

        if (boost::beast::websocket::is_upgrade(m_parser->get()))
        {
            HTTP_SESSION(INFO) << LOG_BADGE("onRead") << LOG_DESC("websocket upgrade");
            if (m_wsUpgradeHandler)
            {
                m_wsUpgradeHandler(m_httpStream, m_parser->release(), nodeId());
            }
            else
            {
                HTTP_SESSION(WARNING) << LOG_BADGE("onRead")
                                      << LOG_DESC(
                                             "the session will be closed for "
                                             "unsupported websocket upgrade");
                close();
                return;
            }
            return;
        }

        HTTP_SESSION(TRACE) << LOG_BADGE("onRead") << LOG_DESC("receive http request");
        auto request = m_parser->release();
        handleRequest(request);
    }
    catch (...)
    {
        HTTP_SESSION(WARNING) << LOG_DESC("onRead exception")
                              << LOG_KV("bytesSize", bytes_transferred)
                              << LOG_KV("error", boost::current_exception_diagnostic_information());
    }

    if (!m_queue.isFull())
    {
        read();
    }
}
void bcos::boostssl::http::HttpSession::onWrite(
    bool closeOnEOF, boost::beast::error_code ec, [[maybe_unused]] std::size_t bytes_transferred)
{
    if (ec)
    {
        HTTP_SESSION(WARNING) << LOG_BADGE("onWrite") << LOG_DESC("close the connection")
                              << LOG_KV("failed", ec);
        close();
        return;
    }

    if (closeOnEOF)
    {
        // we should close the connection, usually because
        // the response indicated the "Connection: close" semantic.
        close();
        return;
    }

    if (m_queue.onWrite())
    {
        // read the next request
        read();
    }
}
void bcos::boostssl::http::HttpSession::close()
{
    if (m_httpStream)
    {
        m_httpStream->close();
    }
}
void bcos::boostssl::http::HttpSession::handleRequest(const HttpRequest& _httpRequest)
{
    HTTP_SESSION(DEBUG) << LOG_BADGE("handleRequest") << LOG_DESC("request")
                        << LOG_KV("method", _httpRequest.method_string())
                        << LOG_KV("target", _httpRequest.target())
                        << LOG_KV("body", _httpRequest.body())
                        << LOG_KV("keep_alive", _httpRequest.keep_alive())
                        << LOG_KV("need_eof", _httpRequest.need_eof());

    auto startT = utcTime();
    unsigned version = _httpRequest.version();

    // handle options request for CORS preflight
    if (_httpRequest.method() == boost::beast::http::verb::options)
    {
        // The standard response status code for preflight requests is 204(No Content)
        auto resp = buildHttpResp(boost::beast::http::status::no_content, _httpRequest.keep_alive(),
            version, {}, m_corsConfig);

        BCOS_LOG(TRACE) << LOG_BADGE("handleRequest") << LOG_DESC("options response")
                        << LOG_KV("body", std::string_view((const char*)resp->body().data(),
                                              resp->body().size()))
                        << LOG_KV("keep_alive", resp->keep_alive())
                        << LOG_KV("need_eof", resp->need_eof())
                        << LOG_KV("timecost", (utcTime() - startT));

        queue().enqueue(std::move(resp));
        return;
    }

    // handle http request
    if (m_httpReqHandler)
    {
        const std::string& request = _httpRequest.body();
        m_httpReqHandler(request, [session = shared_from_this(), version, startT,
                                      keepAlive = _httpRequest.keep_alive()](bcos::bytes _content) {
            auto resp = session->buildHttpResp(boost::beast::http::status::ok, keepAlive, version,
                std::move(_content), session->corsConfig());
            // put the response into the queue and waiting to be send
            BCOS_LOG(TRACE) << LOG_BADGE("handleRequest") << LOG_DESC("response")
                            << LOG_KV("body", std::string_view((const char*)resp->body().data(),
                                                  resp->body().size()))
                            << LOG_KV("keep_alive", resp->keep_alive())
                            << LOG_KV("need_eof", resp->need_eof())
                            << LOG_KV("timecost", (utcTime() - startT));
            session->queue().enqueue(std::move(resp));
        });
    }
    else
    {
        // unsupported http service
        auto resp = buildHttpResp(boost::beast::http::status::http_version_not_supported,
            _httpRequest.keep_alive(), version, {}, m_corsConfig);
        // put the response into the queue and waiting to be send
        HTTP_SESSION(WARNING) << LOG_BADGE("handleRequest") << LOG_DESC("unsupported http service")
                              << LOG_KV("body", std::string_view((const char*)resp->body().data(),
                                                    resp->body().size()));
        queue().enqueue(std::move(resp));
    }
}
bcos::boostssl::http::HttpResponsePtr bcos::boostssl::http::HttpSession::buildHttpResp(
    boost::beast::http::status status, bool keepAlive, unsigned version, bcos::bytes content,
    const CorsConfig& corsConfig) const
{
    auto msg = std::make_shared<HttpResponse>(status, version);
    msg->set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
    msg->set(boost::beast::http::field::content_type, "application/json");
    msg->keep_alive(keepAlive);  // default , keep alive
    msg->body() = std::move(content);

    // set cors config
    if (corsConfig.enableCORS)
    {
        msg->set(boost::beast::http::field::access_control_allow_origin, corsConfig.allowedOrigins);
        msg->set(
            boost::beast::http::field::access_control_allow_methods, corsConfig.allowedMethods);
        msg->set(
            boost::beast::http::field::access_control_allow_headers, corsConfig.allowedHeaders);
        msg->set(
            boost::beast::http::field::access_control_max_age, std::to_string(corsConfig.maxAge));
        msg->set(boost::beast::http::field::access_control_allow_credentials,
            corsConfig.allowCredentials ? "true" : "false");
    }

    // TODO: limit response body size

    msg->prepare_payload();
    return msg;
}
bcos::boostssl::http::HttpReqHandler bcos::boostssl::http::HttpSession::httpReqHandler() const
{
    return m_httpReqHandler;
}
void bcos::boostssl::http::HttpSession::setRequestHandler(HttpReqHandler _httpReqHandler)
{
    m_httpReqHandler = std::move(_httpReqHandler);
}
bcos::boostssl::http::WsUpgradeHandler bcos::boostssl::http::HttpSession::wsUpgradeHandler() const
{
    return m_wsUpgradeHandler;
}
void bcos::boostssl::http::HttpSession::setWsUpgradeHandler(WsUpgradeHandler _wsUpgradeHandler)
{
    m_wsUpgradeHandler = std::move(_wsUpgradeHandler);
}
bcos::boostssl::http::Queue& bcos::boostssl::http::HttpSession::queue()
{
    return m_queue;
}
void bcos::boostssl::http::HttpSession::setQueue(Queue _queue)
{
    m_queue = std::move(_queue);
}
bcos::boostssl::http::HttpStream::Ptr bcos::boostssl::http::HttpSession::httpStream()
{
    return m_httpStream;
}
void bcos::boostssl::http::HttpSession::setHttpStream(HttpStream::Ptr _httpStream)
{
    m_httpStream = std::move(_httpStream);
}
std::shared_ptr<std::string> bcos::boostssl::http::HttpSession::nodeId()
{
    return m_nodeId;
}
void bcos::boostssl::http::HttpSession::setNodeId(std::shared_ptr<std::string> _nodeId)
{
    m_nodeId = std::move(_nodeId);
}
